Passed
Branch wavefile-rw (0c075d)
by Rafael S.
02:29
created

WaveFileConverter.correctContainer_   A

Complexity

Conditions 2

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 2
1
/*
2
 * Copyright (c) 2017-2019 Rafael da Silva Rocha.
3
 *
4
 * Permission is hereby granted, free of charge, to any person obtaining
5
 * a copy of this software and associated documentation files (the
6
 * "Software"), to deal in the Software without restriction, including
7
 * without limitation the rights to use, copy, modify, merge, publish,
8
 * distribute, sublicense, and/or sell copies of the Software, and to
9
 * permit persons to whom the Software is furnished to do so, subject to
10
 * the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be
13
 * included in all copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
 *
23
 */
24
25
/**
26
 * @fileoverview The WaveFileConverter class.
27
 * @see https://github.com/rochars/wavefile
28
 */
29
30
/** @module wavefile */
31
32
import bitDepthLib from 'bitdepth';
33
import * as imaadpcm from 'imaadpcm';
34
import * as alawmulaw from 'alawmulaw';
35
import {unpackArray, unpackArrayTo} from 'byte-data';
36
import WaveFileCreator from './wavefile-creator';
37
import truncateSamples from './truncate-samples';
38
39
/**
40
 * A class to manipulate wav files.
41
 */
42
export default class WaveFileConverter extends WaveFileCreator {
43
44
  /**
45
   * Force a file as RIFF.
46
   */
47
  toRIFF() {
48
    this.fromScratch(
49
      this.fmt.numChannels,
50
      this.fmt.sampleRate,
51
      this.bitDepth,
52
      unpackArray(this.data.samples, this.dataType));
53
  }
54
55
  /**
56
   * Force a file as RIFX.
57
   */
58
  toRIFX() {
59
    this.fromScratch(
60
      this.fmt.numChannels,
61
      this.fmt.sampleRate,
62
      this.bitDepth,
63
      unpackArray(this.data.samples, this.dataType),
64
      {container: 'RIFX'});
65
  }
66
67
  /**
68
   * Encode a 16-bit wave file as 4-bit IMA ADPCM.
69
   * @throws {Error} If sample rate is not 8000.
70
   * @throws {Error} If number of channels is not 1.
71
   */
72
  toIMAADPCM() {
73
    if (this.fmt.sampleRate !== 8000) {
74
      throw new Error(
75
        'Only 8000 Hz files can be compressed as IMA-ADPCM.');
76
    } else if (this.fmt.numChannels !== 1) {
77
      throw new Error(
78
        'Only mono files can be compressed as IMA-ADPCM.');
79
    } else {
80
      this.assure16Bit_();
81
      /** @type {!Int16Array} */
82
      let output = new Int16Array(this.data.samples.length / 2);
83
      unpackArrayTo(this.data.samples, this.dataType, output);
84
      this.fromScratch(
85
        this.fmt.numChannels,
86
        this.fmt.sampleRate,
87
        '4',
88
        imaadpcm.encode(output),
89
        {container: this.correctContainer_()});
90
    }
91
  }
92
93
  /**
94
   * Decode a 4-bit IMA ADPCM wave file as a 16-bit wave file.
95
   * @param {string} bitDepthCode The new bit depth of the samples.
96
   *    One of '8' ... '32' (integers), '32f' or '64' (floats).
97
   *    Optional. Default is 16.
98
   */
99
  fromIMAADPCM(bitDepthCode='16') {
100
    this.fromScratch(
101
      this.fmt.numChannels,
102
      this.fmt.sampleRate,
103
      '16',
104
      imaadpcm.decode(this.data.samples, this.fmt.blockAlign),
105
      {container: this.correctContainer_()});
106
    if (bitDepthCode != '16') {
107
      this.toBitDepth(bitDepthCode);
108
    }
109
  }
110
111
  /**
112
   * Encode a 16-bit wave file as 8-bit A-Law.
113
   */
114
  toALaw() {
115
    this.assure16Bit_();
116
    /** @type {!Int16Array} */
117
    let output = new Int16Array(this.data.samples.length / 2);
118
    unpackArrayTo(this.data.samples, this.dataType, output);
119
    this.fromScratch(
120
      this.fmt.numChannels,
121
      this.fmt.sampleRate,
122
      '8a',
123
      alawmulaw.alaw.encode(output),
124
      {container: this.correctContainer_()});
125
  }
126
127
  /**
128
   * Decode a 8-bit A-Law wave file into a 16-bit wave file.
129
   * @param {string} bitDepthCode The new bit depth of the samples.
130
   *    One of '8' ... '32' (integers), '32f' or '64' (floats).
131
   *    Optional. Default is 16.
132
   */
133
  fromALaw(bitDepthCode='16') {
134
    this.fromScratch(
135
      this.fmt.numChannels,
136
      this.fmt.sampleRate,
137
      '16',
138
      alawmulaw.alaw.decode(this.data.samples),
139
      {container: this.correctContainer_()});
140
    if (bitDepthCode != '16') {
141
      this.toBitDepth(bitDepthCode);
142
    }
143
  }
144
145
  /**
146
   * Encode 16-bit wave file as 8-bit mu-Law.
147
   */
148
  toMuLaw() {
149
    this.assure16Bit_();
150
    /** @type {!Int16Array} */
151
    let output = new Int16Array(this.data.samples.length / 2);
152
    unpackArrayTo(this.data.samples, this.dataType, output);
153
    this.fromScratch(
154
      this.fmt.numChannels,
155
      this.fmt.sampleRate,
156
      '8m',
157
      alawmulaw.mulaw.encode(output),
158
      {container: this.correctContainer_()});
159
  }
160
161
  /**
162
   * Decode a 8-bit mu-Law wave file into a 16-bit wave file.
163
   * @param {string} bitDepthCode The new bit depth of the samples.
164
   *    One of '8' ... '32' (integers), '32f' or '64' (floats).
165
   *    Optional. Default is 16.
166
   */
167
  fromMuLaw(bitDepthCode='16') {
168
    this.fromScratch(
169
      this.fmt.numChannels,
170
      this.fmt.sampleRate,
171
      '16',
172
      alawmulaw.mulaw.decode(this.data.samples),
173
      {container: this.correctContainer_()});
174
    if (bitDepthCode != '16') {
175
      this.toBitDepth(bitDepthCode);
176
    }
177
  }
178
179
  /**
180
   * Change the bit depth of the samples.
181
   * @param {string} newBitDepth The new bit depth of the samples.
182
   *    One of '8' ... '32' (integers), '32f' or '64' (floats)
183
   * @param {boolean} changeResolution A boolean indicating if the
184
   *    resolution of samples should be actually changed or not.
185
   * @throws {Error} If the bit depth is not valid.
186
   */
187
  toBitDepth(newBitDepth, changeResolution=true) {
188
    /** @type {string} */
189
    let toBitDepth = newBitDepth;
190
    /** @type {string} */
191
    let thisBitDepth = this.bitDepth;
192
    if (!changeResolution) {
193
      if (newBitDepth != '32f') {
194
        toBitDepth = this.dataType.bits.toString();
195
      }
196
      thisBitDepth = this.dataType.bits;
197
    }
198
    this.assureUncompressed_();
199
    /** @type {number} */
200
    let sampleCount = this.data.samples.length / (this.dataType.bits / 8);
201
    /** @type {!Float64Array} */
202
    let typedSamplesInput = new Float64Array(sampleCount);
203
    /** @type {!Float64Array} */
204
    let typedSamplesOutput = new Float64Array(sampleCount);
205
    unpackArrayTo(this.data.samples, this.dataType, typedSamplesInput);
206
    if (thisBitDepth == "32f" || thisBitDepth == "64") {
207
      truncateSamples(typedSamplesInput);
208
    }
209
    bitDepthLib(
210
      typedSamplesInput, thisBitDepth, toBitDepth, typedSamplesOutput);
211
    this.fromScratch(
212
      this.fmt.numChannels,
213
      this.fmt.sampleRate,
214
      newBitDepth,
215
      typedSamplesOutput,
216
      {container: this.correctContainer_()});
217
  }
218
219
  /**
220
   * Make the file 16-bit if it is not.
221
   * @private
222
   */
223
  assure16Bit_() {
224
    this.assureUncompressed_();
225
    if (this.bitDepth != '16') {
226
      this.toBitDepth('16');
227
    }
228
  }
229
230
  /**
231
   * Uncompress the samples in case of a compressed file.
232
   * @private
233
   */
234
  assureUncompressed_() {
235
    if (this.bitDepth == '8a') {
236
      this.fromALaw();
237
    } else if (this.bitDepth == '8m') {
238
      this.fromMuLaw();
239
    } else if (this.bitDepth == '4') {
240
      this.fromIMAADPCM();
241
    }
242
  }
243
244
  /**
245
   * Return 'RIFF' if the container is 'RF64', the current container name
246
   * otherwise. Used to enforce 'RIFF' when RF64 is not allowed.
247
   * @return {string}
248
   * @private
249
   */
250
  correctContainer_() {
251
    return this.container == 'RF64' ? 'RIFF' : this.container;
252
  }
253
}
254